﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  partial class DataGridControl
  {
    private sealed class EditingManager
    {
      #region Constructor

      internal EditingManager( DataGridControl dataGrid )
      {
        if( dataGrid == null )
          throw new ArgumentNullException( "dataGrid" );

        m_dataGrid = dataGrid;
      }

      #endregion

      internal bool CanBeEdited( EditableCellViewModel targetCell )
      {
        return ( targetCell != null )
            && ( !m_dataGrid.ReadOnly )
            && ( !targetCell.Column.ReadOnly.GetValueOrDefault( false ) );
      }

      internal bool BeginEdit( EditableRow targetRow )
      {
        if( targetRow == null )
          throw new ArgumentNullException( "targetRow" );


        return this.BeginEditContainer( targetRow );
      }

      internal bool BeginEdit( EditableRowViewModel targetRow )
      {
        return this.BeginEditCore( targetRow, null );
      }

      internal bool BeginEdit( EditableCell targetCell )
      {
        if( targetCell == null )
          throw new ArgumentNullException( "targetCell" );


        return this.BeginEditContainer( targetCell );
      }

      internal void BeginEditAndCommitValue( EditableCell targetCell )
      {
        if( targetCell == null )
          throw new ArgumentNullException( "targetCell" );

        {
          var viewModel = targetCell.ViewModel;
          if( viewModel == null )
            return;

          bool commitValue = ( viewModel.IsBeingEdited )
                          || ( this.BeginEditContainer( targetCell ) );
          if( !commitValue )
            return;

          if( UpdateSourceTriggerHelper.IsIncludedIn( m_dataGrid.UpdateSourceTrigger, UpdateSourceTrigger.PropertyChanged ) )
          {
            viewModel.CommitValue();
          }
        }
      }

      internal bool EndEdit( EditableRow targetRow )
      {
        if( targetRow == null )
          throw new ArgumentNullException( "targetRow" );

        return this.EndEditCore( targetRow.ViewModel );
      }

      internal bool EndEdit( EditableRowViewModel targetRow )
      {
        return this.EndEditCore( targetRow );
      }

      internal bool EndEdit( EditableCell targetCell )
      {
        if( targetCell == null )
          return false;

        return this.EndEditCore( targetCell.ViewModel );
      }

      internal bool EndEdit( EditableCellViewModel targetCell )
      {
        return this.EndEditCore( targetCell );
      }

      internal bool CancelEdit( EditableRow targetRow )
      {
        if( targetRow == null )
          throw new ArgumentNullException( "targetRow" );

        return this.CancelEditCore( targetRow.ViewModel );
      }

      internal bool CancelEdit( EditableRowViewModel targetRow )
      {
        return this.CancelEditCore( targetRow );
      }

      internal bool CancelEdit( EditableCell targetCell )
      {
        if( targetCell == null )
          throw new ArgumentNullException( "targetCell" );

        return this.CancelEditCore( targetCell.ViewModel );
      }

      internal bool CancelEdit( EditableCellViewModel targetCell )
      {
        return this.CancelEditCore( targetCell );
      }





















      private bool BeginEditContainer( EditableRow targetRow )
      {
        return this.BeginEditContainer( targetRow, null );
      }

      private bool BeginEditContainer( EditableCell targetCell )
      {
        return this.BeginEditContainer( ( EditableRow )targetCell.ParentRow, targetCell );
      }

      private bool BeginEditContainer( EditableRow targetRow, EditableCell targetCell )
      {
        if( !this.IsEditable( targetRow, targetCell ) )
          return false;

        if( targetCell != null )
        {
          var cellViewModel = targetCell.ViewModel;
          if( cellViewModel == null )
            return false;

          return this.BeginEditCore( targetRow.ViewModel, cellViewModel );
        }
        else
        {
          return this.BeginEditCore( targetRow.ViewModel, null );
        }
      }

      private bool BeginEditCore( EditableRowViewModel targetRow, EditableCellViewModel targetCell )
      {
        bool isEditable = ( this.CanBeEdited( targetRow ) )
                       && ( ( targetCell == null ) || ( this.CanBeEdited( targetCell ) ) );

        if( !isEditable )
          return false;

        // Only the cell "editing" process is relevant if the row is already being edited.
        var targetRowEditingState = targetRow.EditingState;
        if( targetRowEditingState == RowEditingState.Edited )
          return this.BeginEditCore( targetCell );

        // The row may only be edited if it is in the "Unchanged" state.
        if( targetRowEditingState != RowEditingState.Unchanged )
          return false;

        bool targetRowEditingSuccess = true;
        bool targetCellEditingSuccess = true;
        var targetRowNewEditingState = targetRowEditingState;

        try
        {
          targetRow.EditingState = RowEditingState.Editing;

          var rowEditBeginningEventArgs = new DataGridEditCancelEventArgs( targetRow, null );
          m_dataGrid.RaiseEditBeginning( rowEditBeginningEventArgs );
          targetRowEditingSuccess = !rowEditBeginningEventArgs.Cancel;

          if( targetRowEditingSuccess )
          {
            var dataItem = targetRow.DataItem;
            var itemsSource = this.GetItemsSource();
            var isNewItem = targetRow.IsNewItem;

            var sourceItemEditBeginningEventArgs = new SourceItemHandledEventArgs( itemsSource, dataItem, isNewItem );
            m_dataGrid.RaiseSourceItemEditBeginning( sourceItemEditBeginningEventArgs );

            if( !sourceItemEditBeginningEventArgs.Handled )
            {
              targetRowEditingSuccess = targetRow.PrepareValues( this.GetEditableCollectionView() );
            }

            if( targetRowEditingSuccess )
            {
              targetRowNewEditingState = RowEditingState.Edited;

              var sourceItemEditBegunEventArgs = new SourceItemEventArgs( itemsSource, dataItem, isNewItem );
              m_dataGrid.RaiseSourceItemEditBegun( sourceItemEditBegunEventArgs );

              if( targetCell != null )
              {
                targetCellEditingSuccess = this.BeginEditCore( targetCell );
              }
            }
          }
        }
        finally
        {
          targetRow.EditingState = targetRowNewEditingState;
        }

        if( targetRowEditingSuccess )
        {
          var rowEditBegunEventArgs = new DataGridEditEventArgs( targetRow, null );
          m_dataGrid.RaiseEditBegun( rowEditBegunEventArgs );

          var inspector = CellEditorVisibilityInspector.Create( m_dataGrid ).Check( targetRow );
          m_dataGrid.UpdateCellsEditorVisibility( inspector );
        }

        return ( targetRowEditingSuccess && targetCellEditingSuccess );
      }

      private bool BeginEditCore( EditableCellViewModel targetCell )
      {
        if( ( targetCell == null ) || ( targetCell.EditingState != CellEditingState.Unchanged ) )
          return false;

        bool success = true;

        var newEditingState = targetCell.EditingState;
        targetCell.EditingState = CellEditingState.Editing;

        try
        {
          var cellEditBeginningEventArgs = new DataGridEditCancelEventArgs( targetCell, null );
          m_dataGrid.RaiseEditBeginning( cellEditBeginningEventArgs );
          success = !cellEditBeginningEventArgs.Cancel;

          if( success )
          {
            newEditingState = CellEditingState.Edited;
          }
        }
        finally
        {
          targetCell.EditingState = newEditingState;
        }

        if( success )
        {
          var cellEditBegunEventArgs = new DataGridEditEventArgs( targetCell, null );
          m_dataGrid.RaiseEditBegun( cellEditBegunEventArgs );

          var inspector = CellEditorVisibilityInspector.Create( m_dataGrid ).Check( targetCell );
          m_dataGrid.UpdateCellsEditorVisibility( inspector );
        }

        return success;
      }

      private bool EndEditCore( EditableRowViewModel targetRow )
      {
        if( ( targetRow == null ) || ( targetRow.EditingState != RowEditingState.Edited ) )
          return false;

        bool success = true;

        var newEditingState = targetRow.EditingState;
        targetRow.EditingState = RowEditingState.Ending;

        try
        {
          var rowEditEndingEventArgs = new DataGridEditCancelEventArgs( targetRow, null );
          m_dataGrid.RaiseEditEnding( rowEditEndingEventArgs );
          success = !rowEditEndingEventArgs.Cancel;

          if( success )
          {
            bool updateSource = UpdateSourceTriggerHelper.IsIncludedIn( m_dataGrid.UpdateSourceTrigger, UpdateSourceTrigger.RowEndEdit );

            foreach( var targetCell in targetRow.Cells )
            {
              this.EndEditCore( targetCell );

              if( targetCell.EditingState == CellEditingState.Unchanged )
              {
                if( updateSource )
                {
                  targetCell.CommitValue();
                }
              }
              else
              {
                success = false;
              }
            }

            var itemsSource = this.GetItemsSource();
            var isNewItem = targetRow.IsNewItem;

            if( success )
            {
              var dataItem = targetRow.DataItem;

              var sourceItemEditEndingEventArgs = new SourceItemHandledEventArgs( itemsSource, dataItem, isNewItem );
              m_dataGrid.RaiseSourceItemEditEnding( sourceItemEditEndingEventArgs );

              if( !sourceItemEditEndingEventArgs.Handled )
              {
                success = targetRow.CommitValues( this.GetEditableCollectionView() );
              }

              success &= !targetRow.HasRestrictiveValidationErrors;
            }

            if( success )
            {
              targetRow.EditingState = RowEditingState.Committing;

              var committingStatus = new CommittingStatus( this, targetRow );
              var sourceItemEditEndedEventArgs = new SourceItemEditEndedEventArgs(
                                                   itemsSource,
                                                   targetRow.DataItem,
                                                   isNewItem,
                                                   committingStatus.Callback );
              m_dataGrid.RaiseSourceItemEditEnded( sourceItemEditEndedEventArgs );

              if( !sourceItemEditEndedEventArgs.HandlesResultCallback )
              {
                success = committingStatus.Callback.Invoke( EditResult.Success );
              }

              newEditingState = targetRow.EditingState;
            }
          }
        }
        finally
        {
          targetRow.EditingState = newEditingState;
        }

        // On an insertion row, the model becomes useless once we leave editing mode.
        if( targetRow.IsNewItem && ( targetRow.EditingState == RowEditingState.Unchanged ) )
        {
          targetRow.Clear();
        }

        return success;
      }

      private bool EndEditCore( EditableCellViewModel targetCell )
      {
        if( ( targetCell == null ) || ( targetCell.EditingState != CellEditingState.Edited ) )
          return false;

        bool success = true;

        var newEditingState = targetCell.EditingState;
        targetCell.EditingState = CellEditingState.Committing;

        try
        {
          var cellEditEndingEventArgs = new DataGridEditCancelEventArgs( targetCell, null );
          m_dataGrid.RaiseEditEnding( cellEditEndingEventArgs );
          success = !cellEditEndingEventArgs.Cancel;

          if( success )
          {
            if( UpdateSourceTriggerHelper.IsIncludedIn( m_dataGrid.UpdateSourceTrigger, UpdateSourceTrigger.CellEndEdit ) )
            {
              targetCell.CommitValue();

              success = !targetCell.HasRestrictiveValidationErrors;
            }

            if( success )
            {
              newEditingState = CellEditingState.Unchanged;
            }
          }
        }
        finally
        {
          targetCell.EditingState = newEditingState;
        }

        if( success )
        {
          var cellEditEndedEventArgs = new DataGridEditEventArgs( targetCell, null );
          m_dataGrid.RaiseEditEnded( cellEditEndedEventArgs );

          var inspector = CellEditorVisibilityInspector.Create( m_dataGrid ).Check( targetCell );
          m_dataGrid.UpdateCellsEditorVisibility( inspector );
        }

        return success;
      }

      private bool CancelEditCore( EditableRowViewModel targetRow )
      {
        if( ( targetRow == null ) || ( targetRow.EditingState != RowEditingState.Edited ) )
          return false;

        bool success = true;

        var newEditingState = targetRow.EditingState;
        targetRow.EditingState = RowEditingState.Canceling;

        try
        {
          var rowEditCancelingEventArgs = new DataGridEditCancelEventArgs( targetRow, null );
          m_dataGrid.RaiseEditCanceling( rowEditCancelingEventArgs );
          success = !rowEditCancelingEventArgs.Cancel;

          if( success )
          {
            // Try to cancel edit on every cell of the row.
            foreach( var targetCell in targetRow.Cells )
            {
              this.CancelEditCore( targetCell );

              success &= ( targetCell.EditingState == CellEditingState.Unchanged );
            }

            if( success )
            {
              var dataItem = targetRow.DataItem;
              var itemsSource = this.GetItemsSource();
              var isNewItem = targetRow.IsNewItem;

              var sourceItemEditCancelingEventArgs = new SourceItemHandledEventArgs( itemsSource, dataItem, isNewItem );
              m_dataGrid.RaiseSourceItemEditCanceling( sourceItemEditCancelingEventArgs );

              if( !sourceItemEditCancelingEventArgs.Handled )
              {
                success = targetRow.RollbackValues( this.GetEditableCollectionView() );
              }

              if( success )
              {
                newEditingState = RowEditingState.Unchanged;

                var sourceItemEditCanceledEventArgs = new SourceItemEventArgs( itemsSource, dataItem, isNewItem );
                m_dataGrid.RaiseSourceItemEditCanceled( sourceItemEditCanceledEventArgs );
              }
            }
          }
        }
        finally
        {
          targetRow.EditingState = newEditingState;
        }

        if( success )
        {
          var rowEditCanceledEventArgs = new DataGridEditEventArgs( targetRow, null );
          m_dataGrid.RaiseEditCanceled( rowEditCanceledEventArgs );

          var inspector = CellEditorVisibilityInspector.Create( m_dataGrid ).Check( targetRow );
          m_dataGrid.UpdateCellsEditorVisibility( inspector );
        }

        // On an insertion row, the model becomes useless once we leave editing mode.
        if( targetRow.IsNewItem && ( targetRow.EditingState == RowEditingState.Unchanged ) )
        {
          targetRow.Clear();
        }

        return success;
      }

      private bool CancelEditCore( EditableCellViewModel targetCell )
      {
        if( ( targetCell == null ) || ( targetCell.EditingState != CellEditingState.Edited ) )
          return false;

        bool success = true;

        var newEditingState = targetCell.EditingState;
        targetCell.EditingState = CellEditingState.Canceling;

        try
        {
          var cellEditCancelingEventArgs = new DataGridEditCancelEventArgs( targetCell, null );
          m_dataGrid.RaiseEditCanceling( cellEditCancelingEventArgs );
          success = !cellEditCancelingEventArgs.Cancel;

          if( success )
          {
            targetCell.RollbackValue();

            newEditingState = CellEditingState.Unchanged;
          }
        }
        finally
        {
          targetCell.EditingState = newEditingState;
        }

        if( success )
        {
          var cellEditCanceledEventArgs = new DataGridEditEventArgs( targetCell, null );
          m_dataGrid.RaiseEditCanceled( cellEditCanceledEventArgs );

          var inspector = CellEditorVisibilityInspector.Create( m_dataGrid ).Check( targetCell );
          m_dataGrid.UpdateCellsEditorVisibility( inspector );
        }

        return success;
      }

      private bool CanBeEdited( EditableRowViewModel targetRow )
      {
        if( ( targetRow == null ) || m_dataGrid.ReadOnly )
          return false;

        foreach( var targetCell in targetRow.Cells )
        {
          if( this.CanBeEdited( targetCell ) )
            return true;
        }

        return false;
      }

      private bool IsEditable( EditableRow targetRow, EditableCell targetCell )
      {
        if( m_dataGrid.ReadOnly || ( this.GetItemsSource() == null ) )
          return false;

        if( ( targetRow == null ) || ( targetRow.IsRecycled( m_dataGrid ) ) )
          return false;

        if( targetCell != null )
        {
          var parentColumnContainer = targetCell.ParentColumnContainer;
          if( ( parentColumnContainer == null ) || ( parentColumnContainer.IsRecycled ) )
            return false;

          var parentColumn = targetCell.ParentColumn;
          if( ( parentColumn == null ) || ( parentColumn.ReadOnly.GetValueOrDefault( false ) ) )
            return false;

          var parentRow = targetCell.ParentRow as EditableRow;
          if( ( parentRow == null ) || ( targetCell.IsRecycled( parentRow ) ) )
            return false;
        }

        return true;
      }

      private object GetItemsSource()
      {
        object itemsSource = m_dataGrid.ItemsSource;

        ListDataSourceProvider listDataSourceProvider = itemsSource as ListDataSourceProvider;

        if( listDataSourceProvider != null )
        {
          itemsSource = listDataSourceProvider.DataSource;
        }

        return itemsSource;
      }

      private IEditableCollectionView GetEditableCollectionView()
      {
        return this.GetItemsSource() as IEditableCollectionView;
      }

      #region Private Fields

      private readonly DataGridControl m_dataGrid;

      #endregion

      #region CommittingStatus Nested Type

      private sealed class CommittingStatus
      {
        internal CommittingStatus( EditingManager context, EditableRowViewModel targetRow )
        {
          m_context = context;
          m_targetRow = targetRow;
        }

        internal SourceItemEditEndedDelegate Callback
        {
          get
          {
            return this.Commit;
          }
        }

        private bool Commit( EditResult result )
        {
          // Make sure the callback is called on the same item in the appropriate state.
          if( ( m_resultHandled )
            || ( m_targetRow.EditingState != RowEditingState.Committing ) )
            return false;


          var cellInEditMode = ( from cell in m_targetRow.Cells
                                 where cell.EditingState != CellEditingState.Unchanged
                                 select cell );

          var success = ( !cellInEditMode.Any() )
                     && ( !m_targetRow.HasRestrictiveValidationErrors );

          return this.ChangeState( success );
        }

        private bool ChangeState( bool success )
        {
          m_resultHandled = true;

          if( success )
          {
            m_targetRow.EditingState = RowEditingState.Unchanged;

            var dataGrid = m_context.m_dataGrid;
            var rowEditEndedEventArgs = new DataGridEditEventArgs( m_targetRow, null );
            dataGrid.RaiseEditEnded( rowEditEndedEventArgs );

            var inspector = CellEditorVisibilityInspector.Create( dataGrid ).Check( m_targetRow );
            dataGrid.UpdateCellsEditorVisibility( inspector );

            // On an insertion row, the model becomes useless once we leave editing mode.
            if( m_targetRow.IsNewItem && ( m_targetRow.EditingState == RowEditingState.Unchanged ) )
            {
              m_targetRow.Clear();
            }
          }
          else
          {
            m_targetRow.EditingState = RowEditingState.Edited;
          }

          return success;
        }

        private readonly EditingManager m_context;
        private readonly EditableRowViewModel m_targetRow;
        private bool m_resultHandled;
      }

      #endregion
    }
  }
}
